package sample.websocket.servlet;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.annotation.WebServlet;

import net.arnx.jsonic.JSON;

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.WsOutbound;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@WebServlet(urlPatterns = { "/chat/" })
public class SampleWebsocketServlet extends WebSocketServlet {

	private Log log = LogFactory.getLog(getClass());

	/**
	 * SerialVersionUID.
	 */
	private static final long serialVersionUID = -5071060990676493696L;

	private static final String GUEST_PREFIX = "Guest_%02d";

	private final AtomicInteger ids = new AtomicInteger(0);

	private final Set<SampleMessageInbound> connections = new CopyOnWriteArraySet<>();

	/**
	 * コネクション生成時のメソッド
	 */
	@Override
	protected StreamInbound createWebSocketInbound(String arg0) {
		log.info(arg0);
		return new SampleMessageInbound(ids.incrementAndGet());
	}

	/**
	 * 通信用クラス
	 */
	private class SampleMessageInbound extends MessageInbound {

		private String name;

		public SampleMessageInbound(int id) {
			name = String.format(GUEST_PREFIX, id);
		}

		@Override
		protected void onBinaryMessage(ByteBuffer arg0) throws IOException {
		}

		/**
		 * テキストメッセージ受信時のメソッド
		 */
		@Override
		protected void onTextMessage(CharBuffer message) throws IOException {
			// Never trust the client
			log.info(message.toString());
			Message m = JSON.decode(message.toString(), Message.class);
			String filteredMessage = String.format("%s: %s", name, m.data);

			log.info(filteredMessage);
			broadcast(filteredMessage);
		}

		/**
		 * コネクション開始のメソッド
		 */
		protected void onOpen(WsOutbound outbound) {
			connections.add(this);
			String message = String.format("%s %s", name, "has joined.");
			log.info(message);
			broadcast(message);
		}

		/**
		 * コネクション終了のメソッド
		 */
		@Override
		protected void onClose(int status) {
			connections.remove(this);
			String message = String.format("%s %s", name, "has disconnected.");
			log.info(message);
			broadcast(message);
		}

		private void broadcast(String message) {
			log.info(message);
			for (SampleMessageInbound connection : connections) {
				try {
					CharBuffer buffer = CharBuffer.wrap(JSON
							.encode(new Message(message)));
					log.info(JSON.encode(new Message(message)));
					connection.getWsOutbound().writeTextMessage(buffer);
				} catch (IOException e) {
					log.error(e, e);
				}
			}
		}
	}

	public class Message {
		public Message() {
		}

		public Message(String message) {
			this.type = "message";
			this.data = message;
		}

		public String data;
		public String type;
	}
}
